package com.qf.cx.simplenms.link.impl;

import com.qf.cx.simplenms.link.model.*;
import com.qf.cx.simplenms.link.rai.*;
import com.qf.cx.simplenms.link.rai.model.RaiLimit;
import com.qf.cx.simplenms.link.rai.model.ReadCustomResult;
import com.qf.cx.simplenms.link.rai.model.ReadOpInfo;
import com.qf.cx.simplenms.link.rai.model.ReadOpResult;
import com.qf.cx.simplenms.link.rai.model.RwOpInfo;
import com.qf.cx.simplenms.link.rai.model.RwPara;
import com.qf.cx.simplenms.link.rai.model.RwResult;
import com.qf.cx.simplenms.link.rai.model.TagMaskInfo;
import com.qf.cx.simplenms.link.rai.model.TagResult;
import com.qf.cx.simplenms.link.rai.model.WriteOpInfo;
import com.qf.cx.simplenms.link.rai.model.WriteOpResult;
import com.qf.cx.simplenms.link.rai.protobuf.RaiMsg;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Created by linda on 2016/11/12.
 */
public class PhysicalLinkImpl
        implements PhysicalLinkIF {
	
	private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private int linkHandler;
    private boolean avaibale = true;
    private boolean canBeDisconnect = true;
    private List<Integer> opTypes;
    private RaiLibrary libInstance;
    RwResult.ByReference rwresult;

    PhysicalLinkImpl(int linkHandler, RaiLibrary raiLibrary) {
    	logger.info("a new phy link,linkHandler = {}",linkHandler);
        this.linkHandler = linkHandler;
        this.libInstance = raiLibrary;

    }

    private synchronized void setUnavailable() {
    	logger.info("phy link set unavailable,linkHandler = {}",linkHandler);    	
        this.avaibale = false;
    }

    @Override
    public synchronized SendCommandResultEnum startOp(OpSpec opSpec, int timespan) {
        if (!avaibale) {
        	logger.warn("phy link {} is unavailable ,start op fail!",linkHandler);
            return SendCommandResultEnum.LINK_ERROR;
        }
        RwPara.ByReference rwPara = opSpec2RwPara(opSpec);
        opTypes = getTagOpType(opSpec);
        int ret = libInstance.rai_startPeriodOp(linkHandler, rwPara, timespan);
        if (ret == 0) {
        	logger.info("phy link {} start op success!",linkHandler);
        	logger.debug("op para :{}", rwPara);
        	logger.debug("op types :{}", opTypes);  
        	rwresult = initRwResult();
            return SendCommandResultEnum.SUCCESS;
        }
        if (isConnectBreakReturnCode(ret)) {
        	logger.warn("phy link {} start op fail, link error!",linkHandler);
            setUnavailable();
            return SendCommandResultEnum.LINK_ERROR;
        }
        logger.error("phy link {} start op fail, start failed! err code = {}",linkHandler,ret);
        return SendCommandResultEnum.FAIL;
    }


    @Override
    public synchronized PeriodOpResult raiGetPeriodOpResult(int timeout) {
        if (!avaibale) {
        	logger.warn("phy link {} is unavailable ,get op resault fail!",linkHandler);
            return new PeriodOpResult(SendCommandResultEnum.LINK_ERROR);
        }

        int ret = libInstance.rai_getPeriodOpResult(linkHandler, timeout, rwresult);
        if (ret == 0) {
            OpResultSpec opResultSpec = rwResult2OpResultSpec(rwresult);
            final PeriodOpResult result = new PeriodOpResult(SendCommandResultEnum.SUCCESS);
            result.setOpResultSpec(opResultSpec);
            return result;
        }
        if (isConnectBreakReturnCode(ret)) {
        	logger.warn("phy link {} get op resault fail, link error!",linkHandler);
            setUnavailable();
            return new PeriodOpResult(SendCommandResultEnum.LINK_ERROR);
        }
        if (isRecvTimeoutReturnCode(ret)) {
        	logger.info("phy link {} get op resault fail, link error!",linkHandler);
        }
        else{
        	logger.warn("phy link {} get op resault fail, link error!",linkHandler);        	
        }
        
        return new PeriodOpResult(SendCommandResultEnum.FAIL);
    }

    @Override
    public boolean isOk() {
        return avaibale;
    }

    @Override
    public void setBreadState() {
        this.avaibale = false;
    }

    @Override
    public synchronized void disconnect() {
    	if(!canBeDisconnect){
    		logger.info("phy link {} has disconnect,success!",linkHandler);
    		return;
    	}
        libInstance.rai_disConnect(linkHandler);
        canBeDisconnect = false;
        this.avaibale = false;
    	logger.info("phy link {} disconnect success!",linkHandler);
    }
	@Override
	public synchronized void stopOp() {
        if (!avaibale) {
        	logger.info("phy link {} is unavailable ,no need stop op",linkHandler);
            return;
        }
        libInstance.rai_stopPeriodOp(linkHandler);
        logger.info("phy link {} stop op  success!",linkHandler);
		
	}   
	@Override
	public int linkstatus() {
        if (!avaibale) {
        	logger.warn("phy link {} is unavailable ,link status not ok",linkHandler);
            return 1;
        }
        int status = libInstance.rai_getLinkStatus(linkHandler);
    	logger.info("phy link {} link status:{}",linkHandler,status);
    	return status;
	}

    private OpResultSpec rwResult2OpResultSpec(RwResult.ByReference rwResult) {
        OpResultSpec result = new OpResultSpec();
        List<TagResultSpec> tagResults = result.getTagResults();

        for (int i = 0; i < rwResult.tagNum; i++) {
            TagResultSpec tagResult = new TagResultSpec();
            TagResult tagRes = rwResult.tagResult[i];

            tagResult.setAntennaId(tagRes.antennaId);
            tagResult.setTidData(tagRes.tidData);
            tagResult.setSeenTimeUTC(tagRes.seenTimestampUTC);

            for (int k = 0; k < tagRes.opNum; k++) {

                if (tagRes.opResult[k].roRw == RaiLimit.OP_GENERAL_READ) {
                    // tagRes.opResult[k].rwOpResult.setType(ReadOpResult.class);
                    ReadOpResult rdOpResult = tagRes.opResult[k].rwOpResult.readOpResult;
                    ReadOpResultSpec rdOpSpec = new ReadOpResultSpec();

                    rdOpSpec.setOpId(rdOpResult.opId);
                    rdOpSpec.setResult(rdOpResult.result);
                    rdOpSpec.setLen(rdOpResult.len);
                    rdOpSpec.setData(rdOpResult.data);

                    tagResult.addOpResult(rdOpSpec);
                } else if (tagRes.opResult[k].roRw == RaiLimit.OP_GENERAL_WRITE) {
                    // tagRes.opResult[k].rwOpResult.setType(WriteOpResult.class);
                    WriteOpResult wrOpResult = tagRes.opResult[k].rwOpResult.writeOpResult;
                    WriteOpResultSpec wrOpSpec = new WriteOpResultSpec();

                    wrOpSpec.setOpId(wrOpResult.opId);
                    wrOpSpec.setResult(wrOpResult.result);

                    tagResult.addOpResult(wrOpSpec);
                } else if (tagRes.opResult[k].roRw == RaiLimit.OP_CUSTOM_READ) {
                    // tagRes.opResult[k].rwOpResult.setType(ReadCustomResult.class);
                    ReadCustomResult rdCusResult = tagRes.opResult[k].rwOpResult.readCustomResult;
                    ReadCustomResultSpec rdCusSpec = rcRes2rcSpec(rdCusResult);
                    tagResult.addOpResult(rdCusSpec);
                }
            }

            tagResults.add(tagResult);
        }
        return result;
    }

    private OpResultSpec protoBufMsg2OpResultSpec(byte[] buf,int len) {
        
    	RaiMsg.RwResult rwResult = null;
		ByteArrayInputStream bis = new ByteArrayInputStream(buf,0,len); 
		try {
			rwResult = RaiMsg.RwResult.parseFrom(bis);
		} catch (IOException e) {
			e.printStackTrace();
			logger.error("proto msg decode failed,exception:{},linkHandler = {}!",linkHandler,e.getMessage());
			return null;
		} 

    	OpResultSpec result = new OpResultSpec();
        List<TagResultSpec> tagResults = result.getTagResults();
		for(int i = 0; i < rwResult.getTagResultCount(); i++)
		{
            TagResultSpec tagResult = new TagResultSpec();
            RaiMsg.TagResult  tagRes = rwResult.getTagResult(i);
            
            tagResult.setAntennaId((byte)(tagRes.getAntennaId()));
            tagResult.setSeenTimeUTC(tagRes.getSeenTimestampUTC());
            tagResult.setTidData(tagRes.getTidDataList());
            
            for(int k = 0; k < tagRes.getOpResultCount();k++)
            {
            	if(tagRes.getOpResult(k).getRoRw() == RaiLimit.OP_GENERAL_READ)
            	{
            		ReadOpResultSpec rdOpSpec = new ReadOpResultSpec();
            		RaiMsg.ReadOpResult readOpResult = tagRes.getOpResult(k).getRwOpResult().getReadOpResult();
                    rdOpSpec.setOpId((byte)(readOpResult.getOpId()));
                    rdOpSpec.setResult((byte)(readOpResult.getResult()));
                    rdOpSpec.setLen((byte)(readOpResult.getLen()));
                    rdOpSpec.setData(readOpResult.getDataList());   
                    tagResult.addOpResult(rdOpSpec);
            	}else if(tagRes.getOpResult(k).getRoRw() == RaiLimit.OP_GENERAL_WRITE)
            	{
                    WriteOpResultSpec wrOpSpec = new WriteOpResultSpec();
                    RaiMsg.WriteOpResult  writeOpResult = tagRes.getOpResult(k).getRwOpResult().getWriteOpResult();
                    wrOpSpec.setOpId((byte)(writeOpResult.getOpId()));
                    wrOpSpec.setResult((byte)(writeOpResult.getResult()));
                    tagResult.addOpResult(wrOpSpec);            		
            	}else if(tagRes.getOpResult(k).getRoRw() == RaiLimit.OP_CUSTOM_READ)
            	{ 
                    RaiMsg.ReadCustomResult readCustomResult = tagRes.getOpResult(k).getRwOpResult().getReadCustomResult();
                    ReadCustomResultSpec readCustomResultSpec = msgRcRes2rcSpec(readCustomResult);
                    tagResult.addOpResult(readCustomResultSpec); 
            	}
            	
            	
            }
            
            tagResults.add(tagResult);
		}
    	return result;
    }
    
    private ReadCustomResultSpec msgRcRes2rcSpec(RaiMsg.ReadCustomResult s) {
        ReadCustomResultSpec result = new ReadCustomResultSpec();
        result.setOpId((byte)(s.getOpId()));
        result.setResult((byte)(s.getResult()));
        result.setCid(s.getCidList());
        result.setFpdh(s.getFpdhList());
        result.setSyxz((byte)(s.getSyxz()));
        result.setCcrq(s.getCcrqList());
        result.setCllx(s.getCllxList());
        result.setPl(s.getPlList());
        result.setGl(s.getGlList());
        result.setHpzl((byte)(s.getHpzl()));
        result.setHphmxh(s.getHphmxhList());
        result.setJyyxq(s.getJyyxqList());
        result.setQzbfq((byte)(s.getQzbfq()));
        result.setZkzl(s.getZkzlList());
        result.setCsys((byte)(s.getCsys()));
        result.setHphmxhLen((byte)(s.getHphmxhLen()));

        result.setCidUsed((byte)(s.getCidUsed()));
        result.setFpdhUsed((byte)(s.getFpdhUsed()));
        result.setSyxzUsed((byte)(s.getSyxzUsed()));

        result.setCcrqUsed((byte)(s.getCcrqUsed()));
        result.setCllxUsed((byte)(s.getCllxUsed()));
        result.setGlUsed((byte)(s.getGlUsed()));

        result.setPlUsed((byte)(s.getPlUsed()));
        result.setHpzlUsed((byte)(s.getHpzlUsed()));
        result.setHphmxhUsed((byte)(s.getHphmxhUsed()));

        result.setJyyxqUsed((byte)(s.getJyyxqUsed()));
        result.setQzbfqUsed((byte)(s.getQzbfqUsed()));
        result.setZkzlUsed((byte)(s.getZkzlUsed()));
        result.setCsysUsed((byte)(s.getCsysUsed()));
        return result;
    }

    private ReadCustomResultSpec rcRes2rcSpec(ReadCustomResult s) {
        ReadCustomResultSpec result = new ReadCustomResultSpec();
        result.setOpId(s.opId);
        result.setResult(s.result);
        result.setCid(s.cid);
        result.setFpdh(s.fpdh);
        result.setSyxz(s.syxz);
        result.setCcrq(s.ccrq);
        result.setCllx(s.cllx);
        result.setPl(s.pl);
        result.setGl(s.gl);
        result.setHpzl(s.hpzl);
        result.setHphmxh(s.hphmxh);
        result.setJyyxq(s.jyyxq);
        result.setQzbfq(s.qzbfq);
        result.setZkzl(s.zkzl);
        result.setCsys(s.csys);
        result.setHphmxhLen(s.hphmxhLen);

        result.setCidUsed(s.cidUsed);
        result.setFpdhUsed(s.fpdhUsed);
        result.setSyxzUsed(s.syxzUsed);

        result.setCcrqUsed(s.ccrqUsed);
        result.setCllxUsed(s.cllxUsed);
        result.setGlUsed(s.glUsed);

        result.setPlUsed(s.plUsed);
        result.setHpzlUsed(s.hpzlUsed);
        result.setHphmxhUsed(s.hphmxhUsed);

        result.setJyyxqUsed(s.jyyxqUsed);
        result.setQzbfqUsed(s.qzbfqUsed);
        result.setZkzlUsed(s.zkzlUsed);
        result.setCsysUsed(s.csysUsed);
        return result;
    }


    private List<Integer> getTagOpType(OpSpec opSpec) {
        List<Integer> result = new LinkedList<>();
        List<Object> opInfo = opSpec.getOpInfo();
        for (int i = 0; i < opInfo.size(); i++) {
            Object op = opInfo.get(i);
            if (op instanceof ReadOpSpec) {
                if (((ReadOpSpec) op).getMb() == 0)// 读 user 0
                {
                    result.add(new Integer(RaiLimit.OP_CUSTOM_READ));
                } else// 普通读
                {
                    result.add(new Integer(RaiLimit.OP_GENERAL_READ));
                }
            }

            if (op instanceof WriteOpSpec)// 普通写
            {
                result.add(new Integer(RaiLimit.OP_GENERAL_WRITE));
            }
        }
        return result;
    }

    /**
     * 根据ops中存储的op类型，对rwresult中的rwOpResult以此进行类型初始化
     * jna中union使用，必须先指定类型，才能使用，否则无法正确解析出值，和C语言的union使用差别很大
     */
    private RwResult.ByReference initRwResult() {
        RwResult.ByReference result = new RwResult.ByReference();
        for (int i = 0; i < RaiLimit.MAX_TAG_NUM; i++) {
            for (int j = 0; j < opTypes.size(); j++) {
                if (opTypes.get(j).intValue() == RaiLimit.OP_GENERAL_READ) {
                    result.tagResult[i].opResult[j].rwOpResult.setType(ReadOpResult.class);
                } else if (opTypes.get(j).intValue() == RaiLimit.OP_GENERAL_WRITE) {
                    result.tagResult[i].opResult[j].rwOpResult.setType(WriteOpResult.class);
                } else if (opTypes.get(j).intValue() == RaiLimit.OP_CUSTOM_READ) {
                    result.tagResult[i].opResult[j].rwOpResult.setType(ReadCustomResult.class);
                }
            }
        }
        return result;
    }

    /**
     * 对象类型转换，将输入的opSpec转换成底层用的rwPara
     */
    private RwPara.ByReference opSpec2RwPara(OpSpec opSpec) {
        RwPara.ByReference result = new RwPara.ByReference();
        Set<Integer> antenna = opSpec.getAntenna();
        for (Integer ant : antenna) {
            if ((ant.intValue() >= 1) && ant.intValue() <= 4) {
                /** 将使用的天线对应的byte置1，1~4号天线对应byte数组的0~3下标 */
                result.antenna.antenna[ant.intValue() - 1] = 1;
            }
        }

        List<TagMemoryMask> tmMask = opSpec.getTmMask();
        if (tmMask.isEmpty()) {
            for (int i = 0; i < RaiLimit.MAX_TM_MASK_NUM; i++) {
                result.tagMaskInfo[i].isUsed = 0;
            }
        } else {
            for (int i = 0; i < tmMask.size(); i++) {
                TagMemoryMask tm = tmMask.get(i);
                TagMaskInfo tmInfo = result.tagMaskInfo[i];

                tmInfo.isUsed = 1;
                tmInfo.memoryType = tm.getMb();
                tmInfo.matchType = tm.getOffset();
                tmInfo.pointer = tm.getMatch();
                tmInfo.dataMaskLen = tm.getDataMaskLen();
                System.arraycopy(tm.getMask(), 0, tmInfo.mask, 0, tmInfo.mask.length);
                System.arraycopy(tm.getData(), 0, tmInfo.data, 0, tmInfo.data.length);
            }
        }

        List<Object> opInfo = opSpec.getOpInfo();
        result.opNum = opInfo.size();
        for (int i = 0; i < opInfo.size(); i++) {
            Object op = opInfo.get(i);
            RwOpInfo rwOp = result.rwOpInf[i];

            if (op instanceof ReadOpSpec) {
                rwOp.opType = 0;
                rwOp.rwInfo.setType(ReadOpInfo.class);
                rwOp.rwInfo.readOpInf.opId = ((ReadOpSpec) op).getOpId();
                rwOp.rwInfo.readOpInf.mb = ((ReadOpSpec) op).getMb();
                rwOp.rwInfo.readOpInf.offset = ((ReadOpSpec) op).getOffset();
                rwOp.rwInfo.readOpInf.len = ((ReadOpSpec) op).getLen();
            }

            if (op instanceof WriteOpSpec) {
                rwOp.opType = 1;
                rwOp.rwInfo.setType(WriteOpInfo.class);
                rwOp.rwInfo.writeOpInfo.opId = ((WriteOpSpec) op).getOpId();
                rwOp.rwInfo.writeOpInfo.mb = ((WriteOpSpec) op).getMb();
                rwOp.rwInfo.writeOpInfo.offset = ((WriteOpSpec) op).getOffset();
                rwOp.rwInfo.writeOpInfo.len = ((WriteOpSpec) op).getLen();
                System.arraycopy(((WriteOpSpec) op).getData(), 0, rwOp.rwInfo.writeOpInfo.data, 0,
                        rwOp.rwInfo.writeOpInfo.data.length);
            }
        }
        return result;
    }

    boolean isConnectBreakReturnCode(int code) {
        return code == -484;
    }
    
    boolean isRecvTimeoutReturnCode(int code) {
        return code == -486;
    }
}



